#include <stdlib.h>
#include <assert.h>

#include "wavstream.h"
#include "wavchunks.h"

void wavstream_create (WavStream *ws, Stream *out_stream, short num_channels,
                       int samples_per_sec, short bits_per_sample)
{
  short tmp2;
  int tmp4;
  stream_create (&ws->stream);

  ws->stream.write = wavstream_write;
  ws->stream.read = wavstream_read;
  ws->stream.seek = wavstream_seek;
  ws->stream.pos = wavstream_pos;
  ws->stream.size = wavstream_size;
  ws->stream.close = wavstream_close;
  ws->out_stream = out_stream;

  /* RIFF chunk */
  out_stream->write (out_stream, "RIFF", 4);
  /* Size place holder */
  out_stream->seek (out_stream, 4, SeekCur);
  out_stream->write (out_stream, "WAVE", 4);

  /* fmt chunk */
  out_stream->write (out_stream, "fmt ", 4);
  tmp4 = 16;
  out_stream->write (out_stream, &tmp4, 4);
  tmp2 = WAVE_FORMAT_PCM;
  out_stream->write (out_stream, &tmp2, 2);
  out_stream->write (out_stream, &num_channels, 2);
  out_stream->write (out_stream, &samples_per_sec, 4);
  ws->sample_size = bits_per_sample / 8;
  tmp4 = samples_per_sec * num_channels * ws->sample_size;
  out_stream->write (out_stream, &tmp4, 4);
  out_stream->write (out_stream, &ws->sample_size, 2);
  out_stream->write (out_stream, &bits_per_sample, 2);

  /* data chunk */
  ws->data_chunk_offset = out_stream->pos (out_stream);
  out_stream->write (out_stream, "data", 4);
  /* Size place holder */
  out_stream->seek (out_stream, 4, SeekCur);
}

int wavstream_audio_data (WavStream *ws)
{
  if (ws->data_chunk_offset + 8 >= ws->stream.size (&ws->stream))
    return -1;

  return ws->data_chunk_offset + 8;
}

void wavstream_write (Stream *s, void *src, int size)
{
  WavStream *ws = WAVSTREAM_CAST (s);
  ws->out_stream->write (ws->out_stream, src, size);
}

int wavstream_read (Stream *s, void *dest, int num_read, int dest_offset)
{
  WavStream *ws = WAVSTREAM_CAST (s);
  return ws->out_stream->read (ws->out_stream, dest, num_read, dest_offset);
}

void wavstream_seek (Stream *s, int offset, SeekWhence whence)
{
  WavStream *ws = WAVSTREAM_CAST (s);
  int npos;
  int diff;

  switch (whence) {
    case SeekSet: npos = offset; break;
    case SeekCur: npos = s->pos (s) + offset; break;
    case SeekEnd: npos = s->size (s) + offset; break;
  }

  diff = npos - s->size (s);

  if (diff > 0) {
    int i;
    char *sample;

    sample = malloc (ws->sample_size);
    assert (sample);

    /* Get the last audio sample in the stream */
    ws->out_stream->seek (ws->out_stream, -ws->sample_size, SeekEnd);
    ws->out_stream->read (ws->out_stream, sample, ws->sample_size, 0);

    for (i = 0; i < diff; ++i)
      ws->out_stream->write (ws->out_stream, sample + (i %ws->sample_size), 1);

    free (sample);

    return;
  }

  ws->out_stream->seek (ws->out_stream, offset, whence);
}

int wavstream_pos (Stream *s)
{
  WavStream *ws = WAVSTREAM_CAST (s);
  return ws->out_stream->pos (ws->out_stream);
}

int wavstream_size (Stream *s)
{
  WavStream *ws = WAVSTREAM_CAST (s);
  return ws->out_stream->size (ws->out_stream);
}

void wavstream_close (Stream *s)
{
  WavStream *ws = WAVSTREAM_CAST (s);
  int size = ws->out_stream->size (ws->out_stream) - 8;
  ws->out_stream->seek (ws->out_stream, 4, SeekSet);
  ws->out_stream->write (ws->out_stream, &size, 4);

  size = ws->out_stream->size (ws->out_stream) - ws->data_chunk_offset - 8;
  ws->out_stream->seek (ws->out_stream, ws->data_chunk_offset + 4, SeekSet);
  ws->out_stream->write (ws->out_stream, &size, 4);
}
